/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "Shared/DBC/DBCFileLoader.h"
#pragma warning(disable:4996)
using namespace Shared::Util;


namespace Shared {
namespace DBC {

    DBCFileLoader::DBCFileLoader()
    {
        _data = NULL;
        _fieldsOffset = NULL;
    }

    bool DBCFileLoader::load(const char* filename, const char* fmt)
    {
        std::uint32_t header;
        delete[] _data;

        FILE* f = fopen(filename, "rb");
        if (!f) { return false; }

        if (fread(&header, 4, 1, f) != 1)                       // Number of records
        {
            fclose(f);
            return false;
        }

        endianConvert(header);
        if (header != 0x43424457)                               //'WDBC'
        {
            fclose(f);
            return false;
        }

        if (fread(&_recordCount, 4, 1, f) != 1)                  // Number of records
        {
            fclose(f);
            return false;
        }

        endianConvert(_recordCount);

        if (fread(&_fieldCount, 4, 1, f) != 1)                   // Number of fields
        {
            fclose(f);
            return false;
        }

        endianConvert(_fieldCount);

        if (fread(&_recordSize, 4, 1, f) != 1)                   // Size of a record
        {
            fclose(f);
            return false;
        }

        endianConvert(_recordSize);

        if (fread(&_stringSize, 4, 1, f) != 1)                   // String size
        {
            fclose(f);
            return false;
        }

        endianConvert(_stringSize);

        _fieldsOffset = new std::uint32_t[_fieldCount];
        _fieldsOffset[0] = 0;
        for (std::uint32_t i = 1; i < _fieldCount; ++i)
        {
            _fieldsOffset[i] = _fieldsOffset[i - 1];
            if (fmt[i - 1] == 'b' || fmt[i - 1] == 'X')         // byte fields
            { _fieldsOffset[i] += 1; }
            else                                                // 4 byte fields (std::int32_t/float/strings)
            { _fieldsOffset[i] += 4; }
        }

        _data = new unsigned char[_recordSize * _recordCount + _stringSize];
        _stringTable = _data + _recordSize * _recordCount;

        if (fread(_data, _recordSize * _recordCount + _stringSize, 1, f) != 1)
        {
            fclose(f);
            return false;
        }

        fclose(f);
        return true;
    }

    DBCFileLoader::~DBCFileLoader()
    {
        delete[] _data;
        delete[] _fieldsOffset;
    }

    DBCFileLoader::Record DBCFileLoader::getRecord(size_t id)
    {
        SHARED_ASSERT(_data);
        return Record(*this, _data + id * _recordSize);
    }

    std::uint32_t DBCFileLoader::getFormatRecordSize(const char* format, std::int32_t* index_pos)
    {
        std::uint32_t recordsize = 0;
        std::int32_t i = -1;
        for (std::uint32_t x = 0; format[x]; ++ x)
        {
            switch (format[x])
            {
            case DBC_FF_FLOAT:
                recordsize += sizeof(float);
                break;
            case DBC_FF_INT:
                recordsize += sizeof(std::uint32_t);
                break;
            case DBC_FF_STRING:
                recordsize += sizeof(char*);
                break;
            case DBC_FF_SORT:
                i = x;
                break;
            case DBC_FF_IND:
                i = x;
                recordsize += sizeof(std::uint32_t);
                break;
            case DBC_FF_BYTE:
                recordsize += sizeof(std::uint8_t);
                break;
            case DBC_FF_LOGIC:
                SHARED_ASSERT(false && "DBC files not have logic field type");
                break;
            case DBC_FF_NA:
            case DBC_FF_NA_BYTE:
                break;
            default:
                SHARED_ASSERT(false && "unknown format character");
                break;
            }
        }

        if (index_pos)
        { *index_pos = i; }

        return recordsize;
    }

    char* DBCFileLoader::autoProduceData(const char* format, std::uint32_t& records, char**& indexTable)
    {
        /*
        format STRING, NA, FLOAT,NA,INT <=>
        struct{
        char* field0,
        float field1,
        int field2
        }entry;

        this func will generate  entry[rows] data;
        */

        typedef char* ptr;
        if (strlen(format) != _fieldCount)
        { return NULL; }

        // get struct size and index pos
        std::int32_t i;
        std::uint32_t recordsize = getFormatRecordSize(format, &i);

        if (i >= 0)
        {
            std::uint32_t maxi = 0;
            // find max index
            for (std::uint32_t y = 0; y < _recordCount; ++y)
            {
                std::uint32_t ind = getRecord(y).getUInt(i);
                if (ind > maxi) { maxi = ind; }
            }

            ++maxi;
            records = maxi;
            indexTable = new ptr[maxi];
            memset(indexTable, 0, maxi * sizeof(ptr));
        }
        else
        {
            records = _recordCount;
            indexTable = new ptr[_recordCount];
        }

        char* dataTable = new char[_recordCount * recordsize];

        std::uint32_t offset = 0;

        for (std::uint32_t y = 0; y < _recordCount; ++y)
        {
            if (i >= 0)
            {
                indexTable[getRecord(y).getUInt(i)] = &dataTable[offset];
            }
            else
            { indexTable[y] = &dataTable[offset]; }

            for (std::uint32_t x = 0; x < _fieldCount; ++x)
            {
                switch (format[x])
                {
                case DBC_FF_FLOAT:
                    *((float*)(&dataTable[offset])) = getRecord(y).getFloat(x);
                    offset += sizeof(float);
                    break;
                case DBC_FF_IND:
                case DBC_FF_INT:
                    *((std::uint32_t*)(&dataTable[offset])) = getRecord(y).getUInt(x);
                    offset += sizeof(std::uint32_t);
                    break;
                case DBC_FF_BYTE:
                    *((std::uint8_t*)(&dataTable[offset])) = getRecord(y).getUInt8(x);
                    offset += sizeof(std::uint8_t);
                    break;
                case DBC_FF_STRING:
                    *((char**)(&dataTable[offset])) = NULL; // will be replaces non-empty or "" strings in AutoProduceStrings
                    offset += sizeof(char*);
                    break;
                case DBC_FF_LOGIC:
                    SHARED_ASSERT(false && "DBC files not have logic field type");
                    break;
                case DBC_FF_NA:
                case DBC_FF_NA_BYTE:
                case DBC_FF_SORT:
                    break;
                default:
                    SHARED_ASSERT(false && "unknown format character");
                    break;
                }
            }
        }

        return dataTable;
    }

    char* DBCFileLoader::autoProduceStrings(const char* format, char* dataTable)
    {
        if (strlen(format) != _fieldCount)
        { return NULL; }

        char* stringPool = new char[_stringSize];
        memcpy(stringPool, _stringTable, _stringSize);

        std::uint32_t offset = 0;

        for (std::uint32_t y = 0; y < _recordCount; ++y)
        {
            for (std::uint32_t x = 0; x < _fieldCount; ++x)
            {
                switch (format[x])
                {
                case DBC_FF_FLOAT:
                    offset += sizeof(float);
                    break;
                case DBC_FF_IND:
                case DBC_FF_INT:
                    offset += sizeof(std::uint32_t);
                    break;
                case DBC_FF_BYTE:
                    offset += sizeof(std::uint8_t);
                    break;
                case DBC_FF_STRING:
                    {
                        // fill only not filled entries
                        char** slot = (char**)(&dataTable[offset]);
                        if (!*slot || !** slot)
                        {
                            const char* st = getRecord(y).getString(x);
                            *slot = stringPool + (st - (const char*)_stringTable);
                        }
                        offset += sizeof(char*);
                        break;
                    }
                case DBC_FF_LOGIC:
                    SHARED_ASSERT(false && "DBC files not have logic field type");
                    break;
                case DBC_FF_NA:
                case DBC_FF_NA_BYTE:
                case DBC_FF_SORT:
                    break;
                default:
                    SHARED_ASSERT(false && "unknown format character");
                    break;
                }
            }
        }

        return stringPool;
    }

}}